package powierzchnie;

import java.awt.Color;
import java.util.Vector;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import nor.TPoint;

/**
 *
 * @author arkadiusz
 */
public class SpojnyObszar implements Comparable<SpojnyObszar> {

    //zamiana ArrayList na Vector dała zysk 2s (z 50,7s do 48,5s)
    private Vector<TPoint> punktyAL = new Vector<TPoint>();
    private double minDistanco = 0.2; // w metrach
    private double minDistancoKW = 0.2 * 0.2; // w metrach^2 
    private Plaszczyzna ABCD = null;
    //co zwraca TPoint.angle ?
    // nie mam pojeca jaka jednosta 
    private double maxKat = Math.toRadians(25.0); //maxKat powinien byc w rad 
    //tylko do debagowania
    private Vector3d sredniWecNor = null;

    /**
     * Tworzenie obszaru na podstawie jednego ziarna
     * @param p - ziarno
     */
    public SpojnyObszar(TPoint p, int maxKatStopnie, double minDistanco) throws Exception {

        this.maxKat = Math.toRadians(maxKatStopnie);
        this.minDistanco = minDistanco;

        if(p.x == 0 && p.y == 0 && p.z == 0)
            throw new Exception("XX 1");
        if(p.getNorVec().length() == 0.0)
            throw new Exception("XX 2");
        this.punktyAL.add(p);
    }

    /**
     * ocenia czy punkt jest blisko na podstawie parametru this.minDistanco
     * @param punkt
     * @return
     */
    public boolean czyBlisko(TPoint punkt) {
        //nowa wersja z przegladniem od tylu daje okolo 100,4 s
        //for(TPoint p : this.punktyAL){ //ta 157s
        for(int i = this.punktyAL.size() - 1; i >= 0; i--) {
            TPoint p = this.punktyAL.get(i);
            //if(p.distance(punkt) < this.minDistanco)
            if(p.odlegloscKW(punkt) < this.minDistancoKW)// ze 104s na 94s
                return true;
        }
        return false;
    }

    /**
     * ocenia czy punkt jest blisko na podstawie parametru this.minDistanco
     * @param punkt
     * @return
     */
    @Deprecated
    public boolean czyBlisko1(TPoint punkt) {
        //nowa wersja z przegladniem od tylu daje okolo 100,4 s
        for(TPoint p : this.punktyAL) //ta 157s
            //for(int i = this.punktyAL.size()-1; i >=0; i--){
            //    TPoint p = this.punktyAL.get(i);
            if(p.distance(punkt) < this.minDistanco)
                //if(p.odlegloscKW(punkt) < this.minDistancoKW)
                return true;
        return false;
    }

    /**
     * Oblicza najmniejsza odleglosc miedzy zadanym punktem a calym
     * obszarem (zbiorem punktow)
     * @param punkt - punkt do ktorego liczona jest odlegolosc
     * @return najmniejsza odelgosc. Jezeli obszar nie ma punktow lub wystapia
     * jakies inne niekontrolowane zachowania to metoda zwraca Double.MAX_VALUE;
     */
    @Deprecated
    public double getOdlegloscMin(TPoint punkt) {
        double min = Double.MAX_VALUE;

        for(TPoint p : punktyAL)
            if(p.distance(punkt) < min)
                min = p.distance(punkt);
        return min;

    }

    /**
     * Oblicza sredni wektor normalny do obszaru
     * @return
     */
    public Vector3d getSredniWektorNor() {
        Vector3d sumV = new Vector3d(0, 0, 0);
        for(TPoint p : this.punktyAL)
            sumV.add(p.getNorVec());
        if(sumV.length() != 0.0)
            sumV.normalize();
        this.sredniWecNor = sumV;
        return sumV;
//        return this.get(0).getNorVec();

    }

    public Color getColor() {
        return this.punktyAL.get(0).getColor();
    }

    public void setColor(Color k) {
        for(TPoint p : this.punktyAL)
            p.setColor(k);
    }

    /**
     * Zwraca minimalna odlegosc miedzy punktami, na podstawie ktrej klasyfijue
     * sie czy punkt nalezy czy nie nalerzy do obszaru.
     * @param minDistanco - odleglosc w metrach!
     */
    public void setRozstaw(double minDistanco) {
        this.minDistanco = minDistanco;
        this.minDistancoKW = minDistanco * minDistanco;
    }

    /**
     * Zwraca minimalna odlegosc miedzy punktami, na podstawie ktrej klasyfijue
     * sie czy punkt nalezy czy nie nalerzy do obszaru.
     * @return - wartosc w metrach
     */
    public double getRozstaw() {
        return this.minDistanco;
    }

    /**
     * Dodaje puntk do zbioru jezeli nowy punkt jest wystarczajaco blisko.
     * @param pp
     * @return PRAWDA jezeli punkt zostal dodany FALSZ jezeli nie.
     */
    public boolean add(TPoint pp) {
        //jezeli wektor/punkt jest zerowy to pasuje do wystkiego
        // i psuje caly algorytm, wiec udajemy, ze dodalilmy
        // ale nie dodajemy
        if(pp.x == 0 && pp.y == 0 && pp.z == 0)
            return true;
        // werja z pomijaniem punktu z wektorem normalnym rownym 0
        Vector3d v = pp.getNorVec();
        if(v.x == 0.0 && v.y == 0.0 && v.z == 0.0)
            //this.punktyAL.add(pp);
            return true;
        //if (this.getOdlegloscMin(pp) < this.minDistanco) {//stara wersja 187s
        if(this.czyBlisko(pp)) {//nowa wersja 157s
            //nie dodamy bo wektor normalny za bardzo odstaje od sredniej z tego
            //obszaru
            Vector3d vs = this.getSredniWektorNor();
            double kat = vs.angle(v);
            if(kat > Math.PI / 2)
                kat = Math.abs(Math.PI - kat);
            if(kat < this.maxKat) {
                this.punktyAL.add(pp);
                //this.punktyAL.add(0, pp);
                return true;
            }
            return false;
        }
        return false;
    }

    /**
     * 
     * @param i
     * @return
     */
    TPoint get(int i) {
        return this.punktyAL.get(i);
    }

    int size() {
        return this.punktyAL.size();
    }

    Vector<TPoint> getAL() {
        return this.punktyAL;
    }

    //=========================================================================
    //Statystyka//
    /**
     *  Oblicza punkt srodka masy dla danego obszaru
     * @return sirodek masy
     */
    public Point3d getSrMasy() {
        Point3d sm = new Point3d(0.0, 0.0, 0.0);
        for(TPoint p : this.punktyAL) {
            sm.x += p.x;
            sm.y += p.y;
            sm.z += p.z;
        }

        sm.x /= this.punktyAL.size();
        sm.y /= this.punktyAL.size();
        sm.z /= this.punktyAL.size();

        return sm;
    }

    /**
     * Oblicza srednia odleglosc od plaszcyzny tj. sumuje wszystkie czastkowe
     * odleglosci i dzieli przez ich liczbe.
     * @return
     */
    public double liczBladSrOdleglosc() {

        Plaszczyzna pl = this.getPlaszczyzna();

        double sumOdl = 0.0;
        for(TPoint p : this.punktyAL)
            sumOdl += pl.odleglosc(p);

        return sumOdl / this.punktyAL.size();
    }

    public double liczBladSrBladKw() {

        Plaszczyzna pl = this.getPlaszczyzna();

        double suma = 0.0;
        for(TPoint p : this.punktyAL)
            suma += (pl.getA() * p.x + pl.getB() * p.y + pl.getC() * p.z + pl.getD()) *
                    (pl.getA() * p.x + pl.getB() * p.y + pl.getC() * p.z + pl.getD());

        return suma / this.punktyAL.size();
    }

    public Plaszczyzna getPlaszczyzna() {
        if(this.ABCD == null)
            this.ABCD = new Plaszczyzna(this.getSredniWektorNor(), this.getSrMasy());
        return this.ABCD;
    }

    /**
     *  Ten wiekszy co ma wiecej punktow
     * @param o
     * @return
     */
    public int compareTo(SpojnyObszar o) {
        return o.size() - this.size();
    }    //=========================================================================
}
